import java.util.*;


/**
 * This is a collection of enum literals which contain
 * the scoring logic for CPC.
 * Each enum literal has a score method.
 */
public enum ScoreCategory
{
    /**
     * Basic set.
     * Score category that returns the number of dice that
     * rolled a number times the number of pips on that side.
     * Aces regards 1s.
     */
	aces
	{
		private String name = "Aces";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			for (int die : dice)
				if (die == 1)
					result += 1;
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Basic set.
     * Score category that returns the number of dice that
     * rolled a number times the number of pips on that side.
     * Deuces regards 2s.
     */
	deuces
	{
		private String name = "Deuces";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			for (int die : dice)
				if (die == 2)
					result += 2;
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Basic set.
     * Score category that returns the number of dice that
     * rolled a number times the number of pips on that side.
     * Treys regards 3s.
     */
	treys
	{
		private String name = "Treys";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			for (int die : dice)
				if (die == 3)
					result += 3;
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Basic set.
     * Score category that returns the number of dice that
     * rolled a number times the number of pips on that side.
     * Fours regards 4s.
     */
	fours
	{
		private String name = "Fours";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			for (int die : dice)
				if (die == 4)
					result += 4;
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Basic set.
     * Score category that returns the number of dice that
     * rolled a number times the number of pips on that side.
     * Fives regards 5s.
     */
	fives
	{
		private String name = "Fives";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			for (int die : dice)
				if (die == 5)
					result += 5;
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Basic set.
     * Score category that returns the number of dice that
     * rolled a number times the number of pips on that side.
     * Sixes regards 6s.
     */
	sixes
	{
		private String name = "Sixes";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			for (int die : dice)
				if (die == 6)
					result += 6;
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice if there are two pairs of same colour.
     */
	twoPairSameColor
	{
		private String name = "Two Pairs Same Color";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			int pairs = 0;
			int color = 0;
			int dieColor = 0;
			Map<Integer,Boolean> recordedDice = new HashMap<Integer,Boolean>();
			
			for (int die : dice)
			{
				// Record score
				result += die;
				
				// If we already have one, we now have a pair
				if (recordedDice.containsKey(die))
				{

					// First, determine the colour of the die
					if (die < 4)
					{
						dieColor = die;
					}
					else
					{
						dieColor = 7 - die;
					}
					
					// If we haven't set our colour, set it now
					if (0 == color)
					{
						color = dieColor;
					}
					
					// If the colour is what we want, score pair
					if (dieColor == color)
					{
						recordedDice.remove(die);
						pairs++;
					}
				}
				else
				{
					recordedDice.put(die, true);
				}
			}
			
			if (pairs < 2) { result = 0; }
			
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice if there are three of a kind.
     */
	threeOfAKind
	{
		private String name = "Three Of A Kind";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			boolean threeOfAKind = false;
			Map<Integer,Integer> recordedDice = new HashMap<Integer,Integer>();
			
			for (int die : dice)
			{
				result += die;
				
				if (recordedDice.containsKey(die))
				{
					if (recordedDice.get(die) == 2)
					{
						threeOfAKind = true;
					}
					else
					{
						recordedDice.put(die, recordedDice.get(die) + 1);
					}
				}
				else
				{
					recordedDice.put(die, 1);
				}
			}
			
			if(!threeOfAKind)
			{
				result = 0;
			}
			
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores 30 points if all dice are consecutive.
     */
	straight
	{
		private String name = "Straight";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			int lastDie = 0;
			
			// Sort array so that ints can be given in any order.
			Collections.sort(dice);
			
			for (int die : dice)
			{
				// If the die is exactly one greater than the last
				// Or this is the first die, and it's a 2
				// Then it's a part of the straight.
				if (die == lastDie + 1 ||
					(lastDie == 0 && die == 2))
				{
					result++;
					lastDie = die;
				}
			}
			
			// If it's not a full straight we reward no points.
			if (result != 5)
			{
				result = 0;
			}
			else
			{
				result = 30;
			}
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores 35 points if all dice are the same colour.
     */
	flush
	{
		private String name = "Flush";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			int color = 0;
			int dieColor = 0;
			
			for (int die : dice)
			{
				// First, determine the colour of the die
				if (die < 4)
				{
					dieColor = die;
				}
				else
				{
					dieColor = 7 - die;
				}
				
				// If we haven't set our colour, set it now
				if (0 == color)
				{
					color = dieColor;
					result += 1;
				}
				// If the colour is what we want, add 1 to matches
				else if (dieColor == color)
				{
					result += 1;
				}
			}

			// If it's not a full flush we reward no points.
			if (result != 5)
			{
				result = 0;
			}
			else
			{
				result = 35;
			}
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice + 15 if two are same and other three are same. 
     */
	fullHouse
	{
		private String name = "Full House";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			boolean foundPair = false;
			boolean foundTriple = false;
			Map<Integer,Integer> recordedDice = new HashMap<Integer,Integer>();
			
			// Count how many dice we have per die face.
			for (int die : dice)
			{
				if (recordedDice.containsKey(die))
				{
					recordedDice.put(die, recordedDice.get(die) + 1);
				}
				else
				{
					recordedDice.put(die, 1);
				}
			}
			
			// For each die face that has dice, test for pair or triple.
			for (int dieFace : recordedDice.keySet())
			{
				// If we found two dice that match, we save that.
				if (recordedDice.get(dieFace) == 2)
				{
					foundPair = true;
					result += dieFace*2;
				}
				// If we found three dice that match, we save that!
				else if (recordedDice.get(dieFace) == 3)
				{
					foundTriple = true;
					result += dieFace*3;
				}
				// We don't care about anyone else. Sorry :(
			}
			
			// If you found a full house you get bonus monies.
			if (foundPair && foundTriple)
			{
				result += 15;
			}
			// Otherwise BLUH.
			else
			{
				result = 0;
			}
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice plus 20 if two are same, three other are same,
     * and all are same colour.
     */
	fullHouseSameColor
	{
		private String name = "Full House Same Color";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			int color = 0;
			int dieColor = 0;
			boolean foundPair = false;
			boolean foundTriple = false;
			Map<Integer,Integer> recordedDice = new HashMap<Integer,Integer>();

			// Count how many dice we have per die face.
			for (int die : dice)
			{
				if (recordedDice.containsKey(die))
				{
					recordedDice.put(die, recordedDice.get(die) + 1);
				}
				else
				{
					recordedDice.put(die, 1);
				}
			}
			
			// For each die face that has dice, test for pair or triple.
			for (int dieFace : recordedDice.keySet())
			{
				// First, determine the colour of the die
				if (dieFace < 4)
				{
					dieColor = dieFace;
				}
				else
				{
					dieColor = 7 - dieFace;
				}
				
				// If we haven't set our colour, set it now
				if (0 == color)
				{
					color = dieColor;
				}
				
				// If the colour is what we want, we can score it
				if (dieColor == color)
				{
					// If we found two dice that match, we save that.
					if (recordedDice.get(dieFace) == 2)
					{
						foundPair = true;
						result += dieFace*2;
					}
					// If we found three dice that match, we save that!
					else if (recordedDice.get(dieFace) == 3)
					{
						foundTriple = true;
						result += dieFace*3;
					}
					// We don't care about anyone else. Sorry :(
				}
			}
			
			// If you found a full house you get bonus monies!
			if (foundPair && foundTriple)
			{
				result += 20;
			}
			// Otherwise BLUH.
			else
			{
				result = 0;
			}
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice plus 25 if any 4 are same.
     */
	fourOfAKind
	{
		private String name = "Four Of A Kind";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			boolean complete = false;
			Map<Integer,Integer> recordedDice = new HashMap<Integer,Integer>();
			
			for (int die : dice)
			{
				result += die;
				
				if (recordedDice.containsKey(die))
				{
					// If we already have three, we just need this one!
					if (recordedDice.get(die) == 3)
					{
						recordedDice.remove(die);
						result += 25;
						complete = true;
					}
					// Otherwise we just found one so add it.
					else
					{
						recordedDice.put(die, recordedDice.get(die) + 1);
					}
				}
				else
				{
					recordedDice.put(die, 1);
				}
			}
			
			if(!complete){result = 0;}
				
			
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice if there are exactly two colours (neither one nor three).
     */
	twoColorsOnly
	{
		private String name = "Two Colors Only";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 15;
			int dieColor = 0;
			int success = 0;
			int[] colours = {0,0,0};
			
			for (int die : dice)
			{
				// First, determine the colour of the die
				if (die < 4)
				{
					dieColor = die;
				}
				else
				{
					dieColor = 7 - die;
				}
				
				// Save that we found this colour 
				colours[dieColor - 1] = 1; 
				
				// Finally, record the value of the die
				result += die;
			}
			
			for (int colour : colours)
			{
				success += colour;
			}
			
			if (success != 2)
			{
				result = 0;
			}
			
        	score = result;
	        return result;
		}
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice.
     */
	chance
	{
		private String name = "Chance";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			
			for (int die : dice)
			{
				result += die;
			}
			
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	},
    /**
     * Lower set.
     * Scores all dice plus 50 if all dice are same.
     */
	cpc
	{
		private String name = "CPC";
		private int score = -1;
		
		public String getName()
		{
			return name;
		}
		
		public int score(ArrayList<Integer> dice)
		{
			int result = 0;
			Map<Integer,Integer> recordedDice = new HashMap<Integer,Integer>();
			boolean complete = false;
			
			for (int die : dice)
			{
				result += die;
				
				if (recordedDice.containsKey(die))
				{
					// If we already have four, we just need this one!
					if (recordedDice.get(die) == 4)
					{
						result += 50;
						complete = true;
					}
					// Otherwise we just found one so add it.
					else
					{
						recordedDice.put(die, recordedDice.get(die) + 1);
					}
				}
				else
				{
					recordedDice.put(die, 1);
				}
			}
			
			if(!complete){result = 0;}
			
			score = result;
	        return result;
	    }
		
		public int getScore()
		{
			return score;
		}
	};
	
	/**
	 * Scores a set of 5 dice as appropriate for the category being scored.
	 * Does not consider rerolls spent or chance dice used.
	 * @param _indexesForScoring A sorted array (least to greatest) of 5 dice to be scored. 
	 * @return The score for that category.
	 */
	abstract public int score(ArrayList<Integer> _indexesForScoring);
	
	/**
	 * Returns a human-readable form of the category name.
	 */
	abstract public String getName();
	
	/**
	 * Returns the score last made.
	 */
	abstract public int getScore();
}
